I’m always looking for ways to take advantage of Silverlight to accomplish tasks that traditional web technologies can’t. Here’s one that will save you money, increase scalability, and improve your user’s experience…
Allowing users to upload images is becoming common place for many websites and applications today. Equally common is the fact that most cameras and phones today produce very large and high quality images. On the other hand, very rarely do you actually want a high resolution / low compression image sitting on your server; nor does the average user know or care enough to manually reduce the size of their image before uploading it to your site.
Therefore, unless you’re lazy or in “get it done” mode, you are probably going to want to shrink the image and even increase the compression on that image before storing it on your server. Doing so will result in faster downloads of that image which means lower bandwidth costs and a better user experience.
The typical (and only option for standard Javascript+HTML based sites) is to do the work on the server. But wouldn’t it be great if you could do this work on the client BEFORE sending it up to your server!? Doing so would 1) decrease the time it takes to upload the image in the first place, 2) decrease the amount of work required by your server; thereby increasing scalability of your site, and 3) decrease the bandwidth used by both you and your user; thereby saving you money.
Silverlight has your answer! Here’s how:
Step 1) Use the OpenFileDialog to acquire the stream that contains the bytes of the image.
OpenFileDialog openDialog = new OpenFileDialog();
openDialog.Filter = “JPEG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg”;
if (openDialog.ShowDialog().GetValueOrDefault(false))
{
using (FileStream stream = openDialog.File.OpenRead())
{
// now you have the filestream
}
}
Step 2) Create a WriteableBitmap from those bytes using a ScaleTransform to shrink the image.
public static WriteableBitmap GetImageSource(Stream stream, double maxWidth, double maxHeight)
{
BitmapImage bmp = new BitmapImage();
bmp.SetSource(stream);
Image img = new Image();
img.Effect = new DropShadowEffect() { ShadowDepth = 0, BlurRadius = 0 };
img.Source = bmp;
double scaleX = 1;
double scaleY = 1;
if (bmp.PixelHeight > maxHeight)
scaleY = maxHeight / bmp.PixelHeight;
if (bmp.PixelWidth > maxWidth)
scaleX = maxWidth / bmp.PixelWidth;
// maintain aspect ratio by picking the most severe scale
double scale = Math.Min(scaleY, scaleX);
return new WriteableBitmap(img, new ScaleTransform() { ScaleX = scale, ScaleY = scale });
}
Note: I’m still investigating the need for setting the Effect property on your image control. While this code does not actually affect the user’s image, it is necessary to get this to work. Maybe a bug in Silverlight 3?
Step 3) Encode the WriteableBitmap object back to a Jpeg using FJCore and set your compression quality.
Stream Source = Encode(wb, 20);
public static Stream Encode(WriteableBitmap bitmap, int quality)
{
//Convert the Image to pass into FJCore
int width = bitmap.PixelWidth;
int height = bitmap.PixelHeight;
int bands = 3;
byte[][,] raster = new byte[bands][,];
for (int i = 0; i < bands; i++)
{
raster[i] = new byte[width, height];
}
for (int row = 0; row < height; row++)
{
for (int column = 0; column < width; column++)
{
int pixel = bitmap.Pixels[width * row + column];
raster[0][column, row] = (byte)(pixel >> 16);
raster[1][column, row] = (byte)(pixel >> 8);
raster[2][column, row] = (byte)pixel;
}
}
ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };
FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);
//Encode the Image as a JPEG
MemoryStream stream = new MemoryStream();
JpegEncoder encoder = new JpegEncoder(img, quality, stream);
encoder.Encode();
//Move back to the start of the stream
stream.Flush();
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
Step 4) Send the stream for the new, potentially MUCH smaller Jpeg up to your server!
byte[] buffer;
using (Stream Source = JpgEncoder.Encode(wb, 50))
{
int bufferSize = Convert.ToInt32(Source.Length);
buffer = new byte[bufferSize];
Source.Read(buffer, 0, bufferSize);
Source.Close();
}
Service1Client service = new Service1Client();
service.SaveImageAsync(buffer);
That’s it! Plug this code into your Silverlight app everywhere you upload images and improve your website!
Download the source code here.
Credits
Kudos to the folks that made FJCore for sharing all their hard work with those of us that would never consider trying to build it ourselves!
Kudos to nokola for finding a way to build a WriteableBitmap object from an image control without touching the VisualTree.
Hi, nice post! Thanks for crediting me in there! I think what will do the trick (without setting the effect property) is WriteableBitmap.Invalidate(); -> after you create the bitmap. Haven’t tried it yet though…
I found a way to eliminate the need for setting .Effect…
Replace:
return new WriteableBitmap(img, new ScaleTransform() { ScaleX = scale, ScaleY = scale });
With:
int newWidth = Convert.ToInt32(bmp.PixelWidth * scale);
int newHeight = Convert.ToInt32(bmp.PixelHeight * scale);
WriteableBitmap result = new WriteableBitmap(newWidth, newHeight);
result.Render(img, new ScaleTransform() { ScaleX = scale, ScaleY = scale });
result.Invalidate();
Return result;
Thanks! This is just what I have been looking for.
Hi Tim,
I have some trouble executing the sample code which i downloaded from the above link. I tried debugging the project, I am getting the error at the line
‘service.SaveImageAsync(buffer);’ in MainPage.xaml.cs and the error is
Web Service call returns “The remote server returned an error: NotFound”
As I am very much new to silverlight and WCF, I dont find any clue on this.
Can you please help me on this.
Note: I am using Visual Studio 2008
hi i tried applying the snippet in my codes and it did write binary data in my test server but somehow when i retrieve it back to the SL app it just displays Black on the image control ofcourse i set my
bitmapimage.setsource(new memorystream(e.result))
image.source = bitmapimage
by the way im using vb.net and i just tried to convert the encoder, can anyone help me out here thanks
@Vamsi, I have 2 ideas for the error calling the service, 1) the port: If you look in ServiceReferences.ClientConfig you’ll notice that the server port is 49914. When you run the app, it could be trying to run the app under a different port. To check look at the url in the browser and make sure it is the same. If not, either change ServiceReferences.ClientConfig or change the debugging properties for the .Web project to run on a “Specific Port” and type in 49914. 2) Make sure the Web project is your startup project. Do this by right clicking on it and selecting “Set as Startup Project”.
@Al, I just tested it (with my changes from my Jan 22 comment) and it worked for me using one of the same images that comes with Windows. My suggestion would be to run my latest version (which now writes to your hard drive from the service) and try with the exact same image you are testing with. If it works, something must have gone wrong with your port. If it doesn’t, it *might* imply a bug in fjcore.
hi Tim thanks for the quick reply, shouldnt i be able to display the data directly from the stream ? i mean i did manage to write the imagebyte to my db as binary, only problem is when getting it back from db and setting the stream as the source of the bitmap image thats when it displays just black in the image control, although im willin to test the latest version which writes first to the client hard drive, although im just curious why we cant just display it in the image control directly from the stream
Al, are you trying to send up the bytes, then download them, then display them or are you just trying to display the same byte array you are sending up to the server? I just tried the later approach and see my small image without problem.
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(new MemoryStream(buffer));
myImage.Source = bitmapImage;
Tim,
I actually tried both, first i tried to view the saved byte-array from the database, so i thought maybe i am not sending the right set of byte-array so i tried viewing the byte-array before i sent it to the db, and still the image is black, i most probably doing it wrong, i will post my converted code from urs maybe i messed up bad there, but before anything else, my way of displaying the images is not by binding it to datagrid or listview, im trying to transform the bytes into image directly into my Image.Control, i am pretty sure ur code works i just dont know how i messed up please feel free to check my code:
one more thing these codes are all in my Module.vb if that has any effect on anything let me know thank you
_
Function GetBytesFromWBMP(ByVal wBMP As WriteableBitmap, ByVal Quality As Integer) As Byte()
Dim JCoder As JpegEncoder
Dim ImageWidth As Integer = wBMP.PixelWidth
Dim ImageHeight As Integer = wBMP.PixelHeight
Dim ImageBands As Integer = 3
Dim ImageRaster As Byte()(,) = New Byte(ImageBands – 1)(,) {}
Dim ImageStream As New MemoryStream
For i As Integer = 0 To ImageBands – 1
ImageRaster(i) = New Byte(ImageWidth – 1, ImageHeight – 1) {}
Next
Try
For Col As Integer = 0 To ImageHeight – 1
For Row As Integer = 0 To ImageWidth – 1
Dim ImagePixel As Integer = wBMP.Pixels(ImageWidth * Row + Col)
ImageRaster(0)(Col, Row) = CByte(ImagePixel >> 16)
ImageRaster(1)(Col, Row) = CByte(ImagePixel >> 8)
ImageRaster(2)(Col, Row) = CByte(ImagePixel)
Next Row, Col
Catch ex As Exception
End Try
Dim ImageModel As ColorModel = New ColorModel
ImageModel.colorspace = ColorSpace.RGB
Dim Image As FluxJpeg.Core.Image = New FluxJpeg.Core.Image(ImageModel, ImageRaster)
JCoder = New JpegEncoder(Image, 100 – Quality, ImageStream)
JCoder.Encode()
ImageStream.Flush()
ImageStream.Seek(0, SeekOrigin.Begin)
Dim ByteResult(ImageStream.Length) As Byte
ImageStream.Read(ByteResult, 0, CInt(ImageStream.Length))
ImageStream.Close()
Return ByteResult
End Function
then here is the ImageResize dereived from your snippet
_
Public Function ImageResizer(ByVal StreamSRC As Stream, ByVal maxW As Double, ByVal maxH As Double) _
As WriteableBitmap
Dim BMP As New BitmapImage
Dim IMG As Controls.Image
IMG = New Controls.Image()
IMG.Effect = New Effects.DropShadowEffect With {.ShadowDepth = 0, .BlurRadius = 0}
IMG.Source = BMP
BMP.SetSource(StreamSRC)
Dim ScaleX As Double = 1
Dim ScaleY As Double = 1
If BMP.PixelHeight > maxH Then
ScaleY = maxH / BMP.PixelHeight
End If
If BMP.PixelWidth > maxW Then
ScaleX = maxW / BMP.PixelWidth
End If
Dim ScaleSize As Double = Math.Min(ScaleY, ScaleX)
‘Dim newWidth As Integer = Convert.ToInt32(BMP.PixelWidth * ScaleSize)
‘Dim newHeight As Integer = Convert.ToInt32(BMP.PixelHeight * ScaleSize)
‘Dim result As New WriteableBitmap(newWidth, newHeight)
‘result.Render(IMG, New ScaleTransform())
‘result.Invalidate()
Return New WriteableBitmap(IMG, New ScaleTransform() With {.ScaleX = ScaleSize, .ScaleY = ScaleSize})
End Function
Hi,
Gr8 post !
I am trying to upload .png image here but finding the result as distrub image like background turned to black and etc.
Can you please let me know what changes will be needed to work this on .png image.
Thanks!
@Tim Greenfield Thanks very much for your workaround. I was getting browser crashes when this code was compiled under Silverlight 3 but executed under Silverlight 4. Resizing in the Render method solved this.
Hi,
I couldn’t save the image to server without opendailog. In my application am capturing image using webcam and try to save it in server using WCFservice. However I oculduploadimage when i select image using open dailog. Any insight would be great help!
thanks,
Veeru
[…] Use Silverlight to resize images and increase compression BEFORE uploading « Programmer Payback K.Murat BAŞEREN […]
Hi,
I found this post very useful, but I have the same problem as Vishal in that the transparent background of PNGs become black.
Is it possible to at least specify a different colour, say white, for this background? Maybe there’s a setting on WriteableBitmap for this?
Thanks
Hi! Can you please tell me how to make images with width=200 pixels?
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Great article !
@Tim Greenfield , your solution works. Without It, I get Catastrophic Error When later trying to call SetSource() on a BitmapImage. Thanks !
img.Effect property is not found. Any help?
JpgEncoder Not Found? Am i missing any dll?
Great stuff Tim. I need to create some small previews and bumped into your blog. Your approach is faster and produces better quality results then the built-in FJcore resizer (both the natural neighbour and anti-aliasing low pass algorithms). Thanks for sharing!
Thank you so much!
I was looking for it.
but I have a problem.
I could save an image in Database by these codes. but I can’t return that Image from database to show in an “Image control”!
what should I do?
Hi
First- thanks for good solution – great job.
I have though an issue when trying to use this for a resize of eg. 100 images, all opened at once, each image 5-7Mb. It starts out normally and resizes the images, but at some point (probably related to memory usage) these lines returns 0 (zero):
int newWidth = Convert.ToInt32(bmp.PixelWidth * scale);
int newHeight = Convert.ToInt32(bmp.PixelHeight * scale);
The scale property is set to one, so the problem is that bmp.PixelWidth and bmp.PixelHeight are equal 0 (it also affects that scaleX and scaleY are not calculated correctly above in the code).
My computer has plenty of memory (8Gb) when running this on about 70 images (loading at once) the mem usage is increasing from approx 4Gb to 5Gb – so there should be more available
Is anyone hitting this issue?
Do you have any ideas for how to solve it?
I have been trying setting variables to null, callin GC.Collect(), but no luck.
-jas
Hi,
Can it be used for other kind of images, like png?
Thanks. it worked for me. I had a png image and could shrink it to a jpg. A huge difference! png 5mb, jpg 70kb, using 50 as quality
Ne se doveriavaite na Elena Vakulenko. Tazi jena e opsnaa za psihicheskoto zdrave na horata.V Balgaria ima mnogo hora postradali mnogo seriozno ot neinite magicheski umenia. Savetvam Vi – izobsto ne se doblijavaite do neia!
My doctor sent me a recipe of yours and now I’m looking through your
other recipes and I’m drooling. Thank you for executing what
you so! If only additional people would eat like this…
The world would be a far better place.
When someone writes an article he/she maintains the idea of a user in his/her brain that how a user can be aware of it.
Thus that’s why this article is amazing. Thanks!
It’s actually a nice and helpful piece of info. I’m glad that
you just shared this helpful information with us. Please keep
us informed like this. Thanks for sharing.
Amazing! Its in fact remarkable article, I have got much
clear idea regarding from this piece of writing.
I’m gone to say to my little brother, that he should also
visit this weblog on regular basis to obtain updated from
hottest gossip.